/**
 * 
 */
package easyservice.network;

import java.util.concurrent.BlockingDeque;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.TimeUnit;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jivesoftware.smack.ConnectionConfiguration;
import org.jivesoftware.smack.PacketListener;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.filter.PacketFilter;
import org.jivesoftware.smack.filter.PacketTypeFilter;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.Packet;
import org.jivesoftware.smack.packet.Presence;

import easyservice.EasyServiceException;
import easyservice.interpreter.CommandInterpreter;
import easyservice.protocol.Command;
import easyservice.protocol.FaultCommand;

/**
 * @author Taciano Pinheiro
 * @author Rodrigo Barbosa Lira
 * 
 */
public class XMPPClientNetworkAgent implements ClientNetworkAgent {
	
	private static Log log = LogFactory.getLog(XMPPClientNetworkAgent.class);

	private XMPPConnection connection;

	private String serverAlias;

	private BlockingDeque<Command> result;

	private NetworkListener networkListener;

	public XMPPClientNetworkAgent(String username, String password, String hostUrl, int hostPort, String serverAlias, NetworkListener networkListener) {
		this.result = new LinkedBlockingDeque<Command>();
		
		this.networkListener = networkListener;
		
		this.serverAlias = serverAlias;
		
		ConnectionConfiguration config = new ConnectionConfiguration(hostUrl, hostPort);
		
		connection = new XMPPConnection(config);
		
		try {
			log.info("Connecting to xmpp server");
			connection.connect();
			
			log.info("Identifying");
			connection.login(username, password);
		
		} catch (XMPPException e) {
			log.error("Error on connection", e);
			throw new EasyServiceException(e);
		}
		
		this.start();
	}

	/**
	 * Start the client and listening for incoming messages.
	 */
	private void start() {
		log.info("Set status to available");
		connection.sendPacket(new Presence(Presence.Type.available));

		log.info("Create a packet filter to listen for new messages");
		PacketFilter filter = new PacketTypeFilter(Message.class);

		log.info("Register a packet collector using the filter we've created");
		connection.createPacketCollector(filter);

		log.info("Create a packet listener");
		PacketListener myListener = new PacketListener() {
			public void processPacket(Packet packet) {
				log.info("Retrieving packet received");
				Message m = (Message) packet;
				
				log.info("Interpreting received message");
				Command command = CommandInterpreter.interpret(m.getBody());
				
				if (command.isSync()) {
					log.info("Receiving sync command");
					result.add(command);
				} else {
					log.info("Receiving async command");
					log.debug("Command received: " + command.generatedString());
					networkListener.onReceiveMessage(command);
				}
			}
		};

		log.info("Register the listener.");
		connection.addPacketListener(myListener, filter);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see easyservice.network.ClientNetworkAgent#sendMessageToServer(java.lang.String,
	 *      int)
	 */
	@Override
	public Command sendMessageToServer(String msg, int timeOut) {
		Message message = new Message();
		message.setTo(serverAlias);
		message.setBody(msg);

		connection.sendPacket(message);
		try {
			Command command = result.pollFirst(timeOut, TimeUnit.MILLISECONDS);
			
			if (command.getClass().equals(FaultCommand.class)) {
				FaultCommand fault = (FaultCommand) command;
				throw new RuntimeException(fault.getMessage());
			}
			
			return command;
		} catch (InterruptedException e) {
			throw new TimeOutException(e);
		}
	}
}